<template>
	<div
		class="el-slider__button-wrapper"
		@mouseenter="handleMouseEnter"
		@mouseleave="handleMouseLeave"
		@mousedown="onButtonDown"
		@touchstart="onButtonDown"
		:class="{ hover: hovering, dragging: dragging }"
		:style="wrapperStyle"
		ref="button"
		tabindex="0"
		@focus="handleMouseEnter"
		@blur="handleMouseLeave"
		@keydown.left="onLeftKeyDown"
		@keydown.right="onRightKeyDown"
		@keydown.down.prevent="onLeftKeyDown"
		@keydown.up.prevent="onRightKeyDown">
		<xTooltip
			placement="top"
			ref="tooltip"
			:popper-class="tooltipClass"
			:disabled="!showTooltip">
			<span slot="content">{{ formatValue }}</span>
			<div class="el-slider__button" :class="{ hover: hovering, dragging: dragging }"></div>
		</xTooltip>
	</div>
</template>
<script lang="ts">
export default async function () {
	return defineComponent({
		name: "ElSliderButton",
		props: {
			value: {
				type: Number,
				default: 0
			},
			vertical: {
				type: Boolean,
				default: false
			},
			tooltipClass: String
		},

		data() {
			return {
				hovering: false,
				dragging: false,
				isClick: false,
				startX: 0,
				currentX: 0,
				startY: 0,
				currentY: 0,
				startPosition: 0,
				newPosition: null,
				oldValue: this.value
			};
		},

		computed: {
			disabled() {
				return this.$parent.sliderDisabled;
			},

			max() {
				return this.$parent.max;
			},

			min() {
				return this.$parent.min;
			},

			step() {
				return this.$parent.step;
			},

			showTooltip() {
				return this.$parent.showTooltip;
			},

			precision() {
				return this.$parent.precision;
			},

			currentPosition() {
				return `${((this.value - this.min) / (this.max - this.min)) * 100}%`;
			},

			enableFormat() {
				return this.$parent.formatTooltip instanceof Function;
			},

			formatValue() {
				return (this.enableFormat && this.$parent.formatTooltip(this.value)) || this.value;
			},

			wrapperStyle() {
				return this.vertical
					? { bottom: this.currentPosition }
					: { left: this.currentPosition };
			}
		},

		watch: {
			dragging(val) {
				this.$parent.dragging = val;
			}
		},

		methods: {
			displayTooltip() {
				this.$refs.tooltip && (this.$refs.tooltip.showPopper = true);
			},

			hideTooltip() {
				this.$refs.tooltip && (this.$refs.tooltip.showPopper = false);
			},

			handleMouseEnter() {
				this.hovering = true;
				this.displayTooltip();
			},

			handleMouseLeave() {
				this.hovering = false;
				this.hideTooltip();
			},

			onButtonDown(event) {
				if (this.disabled) return;
				event.preventDefault();
				this.onDragStart(event);
				window.addEventListener("mousemove", this.onDragging);
				window.addEventListener("touchmove", this.onDragging);
				window.addEventListener("mouseup", this.onDragEnd);
				window.addEventListener("touchend", this.onDragEnd);
				window.addEventListener("contextmenu", this.onDragEnd);
			},
			onLeftKeyDown() {
				if (this.disabled) return;
				this.newPosition =
					parseFloat(this.currentPosition) - (this.step / (this.max - this.min)) * 100;
				this.setPosition(this.newPosition);
				this.$parent.emitChange();
			},
			onRightKeyDown() {
				if (this.disabled) return;
				this.newPosition =
					parseFloat(this.currentPosition) + (this.step / (this.max - this.min)) * 100;
				this.setPosition(this.newPosition);
				this.$parent.emitChange();
			},
			onDragStart(event) {
				this.dragging = true;
				this.isClick = true;
				if (event.type === "touchstart") {
					event.clientY = event.touches[0].clientY;
					event.clientX = event.touches[0].clientX;
				}
				if (this.vertical) {
					this.startY = event.clientY;
				} else {
					this.startX = event.clientX;
				}
				this.startPosition = parseFloat(this.currentPosition);
				this.newPosition = this.startPosition;
			},

			onDragging(event) {
				if (this.dragging) {
					this.isClick = false;
					this.displayTooltip();
					this.$parent.resetSize();
					let diff = 0;
					if (event.type === "touchmove") {
						event.clientY = event.touches[0].clientY;
						event.clientX = event.touches[0].clientX;
					}
					if (this.vertical) {
						this.currentY = event.clientY;
						diff = ((this.startY - this.currentY) / this.$parent.sliderSize) * 100;
					} else {
						this.currentX = event.clientX;
						diff = ((this.currentX - this.startX) / this.$parent.sliderSize) * 100;
					}
					this.newPosition = this.startPosition + diff;
					this.setPosition(this.newPosition);
				}
			},

			onDragEnd() {
				if (this.dragging) {
					/*
					 * 防止在 mouseup 后立即触发 click，导致滑块有几率产生一小段位移
					 * 不使用 preventDefault 是因为 mouseup 和 click 没有注册在同一个 DOM 上
					 */
					setTimeout(() => {
						this.dragging = false;
						this.hideTooltip();
						if (!this.isClick) {
							this.setPosition(this.newPosition);
							this.$parent.emitChange();
						}
					}, 0);
					window.removeEventListener("mousemove", this.onDragging);
					window.removeEventListener("touchmove", this.onDragging);
					window.removeEventListener("mouseup", this.onDragEnd);
					window.removeEventListener("touchend", this.onDragEnd);
					window.removeEventListener("contextmenu", this.onDragEnd);
				}
			},

			setPosition(newPosition) {
				if (newPosition === null || isNaN(newPosition)) return;
				if (newPosition < 0) {
					newPosition = 0;
				} else if (newPosition > 100) {
					newPosition = 100;
				}
				const lengthPerStep = 100 / ((this.max - this.min) / this.step);
				const steps = Math.round(newPosition / lengthPerStep);
				let value = steps * lengthPerStep * (this.max - this.min) * 0.01 + this.min;
				value = parseFloat(value.toFixed(this.precision));
				this.$emit("input", value);
				this.$nextTick(() => {
					this.displayTooltip();
					this.$refs.tooltip && this.$refs.tooltip.updatePopper();
				});
				if (!this.dragging && this.value !== this.oldValue) {
					this.oldValue = this.value;
				}
			}
		}
	});
}
</script>
